package com.dzt.androidframework.rxpermission;

import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;

import java.util.ArrayList;
import java.util.List;

import rx.Observable;
import rx.functions.Func1;
import rx.subjects.PublishSubject;

/**
 * Created by M02323 on 2017/12/12.
 */

public class RxPermissions {
	static final String TAG = "RxPermissions";

	RxPermissionsFragment mRxPermissionsFragment;

	public RxPermissions(@NonNull AppCompatActivity activity) {
		mRxPermissionsFragment = getRxPermissionsFragment(activity);
	}

	private RxPermissionsFragment getRxPermissionsFragment(AppCompatActivity activity) {
		RxPermissionsFragment rxPermissionsFragment = findRxPermissionsFragment(activity);
		boolean isNewInstance = rxPermissionsFragment == null;
		if (isNewInstance) {
			rxPermissionsFragment = new RxPermissionsFragment();
			FragmentManager fragmentManager = activity.getSupportFragmentManager();

			fragmentManager
					.beginTransaction()
					.add(rxPermissionsFragment, TAG)
					.commitAllowingStateLoss();
			fragmentManager.executePendingTransactions();
		}
		return rxPermissionsFragment;
	}

	private RxPermissionsFragment findRxPermissionsFragment(AppCompatActivity activity) {
		return (RxPermissionsFragment) activity.getSupportFragmentManager().findFragmentByTag(TAG);
	}

	public void setLogging(boolean logging) {
		mRxPermissionsFragment.setLogging(logging);
	}

	/**
	 * Map emitted items from the source observable into {@code true} if permissions in parameters
	 * are granted, or {@code false} if not.
	 * <p>
	 * If one or several permissions have never been requested, invoke the related framework method
	 * to ask the user if he allows the permissions.
	 */
	@SuppressWarnings("WeakerAccess")
	public Observable.Transformer<Object, Boolean> ensure(final String... permissions) {
		return new Observable.Transformer<Object, Boolean>() {
			@Override
			public Observable<Boolean> call(Observable<Object> o) {
				return request(o, permissions)
						// Transform Observable<Permission> to Observable<Boolean>
						.buffer(permissions.length)
						.flatMap(new Func1<List<Permission>, Observable<Boolean>>() {
							@Override
							public Observable<Boolean> call(List<Permission> permissions) {
								if (permissions.isEmpty()) {
									// Occurs during orientation change, when the subject receives onComplete.
									// In that case we don't want to propagate that empty list to the
									// subscriber, only the onComplete.
									return Observable.empty();
								}
								// Return true if all permissions are granted.
								for (Permission p : permissions) {
									if (!p.granted) {
										return Observable.just(false);
									}
								}
								return Observable.just(true);
							}
						});
			}
		};
	}

	/**
	 * Map emitted items from the source observable into {@link Permission} objects for each
	 * permission in parameters.
	 * <p>
	 * If one or several permissions have never been requested, invoke the related framework method
	 * to ask the user if he allows the permissions.
	 */
	@SuppressWarnings("WeakerAccess")
	public Observable.Transformer<Object, Permission> ensureEach(final String... permissions) {
		return new Observable.Transformer<Object, Permission>() {
			@Override
			public Observable<Permission> call(Observable<Object> o) {
				return request(o, permissions);
			}
		};
	}

	/**
	 * Request permissions immediately, <b>must be invoked during initialization phase
	 * of your application</b>.
	 */
	@SuppressWarnings({"WeakerAccess", "unused"})
	public Observable<Boolean> request(final String... permissions) {
		return Observable.just(null).compose(ensure(permissions));
	}

	/**
	 * Request permissions immediately, <b>must be invoked during initialization phase
	 * of your application</b>.
	 */
	@SuppressWarnings({"WeakerAccess", "unused"})
	public Observable<Permission> requestEach(final String... permissions) {
		return Observable.just(null).compose(ensureEach(permissions));
	}

	Observable<Permission> request(final Observable<?> trigger, final String... permissions) {
		if (permissions == null || permissions.length == 0) {
			throw new IllegalArgumentException("RxPermissions.request/requestEach requires at least one input permission");
		}
		return oneOf(trigger, pending(permissions))
				.flatMap(new Func1<Object, Observable<Permission>>() {
					@Override
					public Observable<Permission> call(Object o) {
						return requestImplementation(permissions);
					}
				});
	}

	private Observable<?> pending(final String... permissions) {
		for (String p : permissions) {
			if (!mRxPermissionsFragment.containsByPermission(p)) {
				return Observable.empty();
			}
		}
		return Observable.just(null);
	}

	private Observable<?> oneOf(Observable<?> trigger, Observable<?> pending) {
		if (trigger == null) {
			return Observable.just(null);
		}
		return Observable.merge(trigger, pending);
	}

	@TargetApi(Build.VERSION_CODES.M)
	Observable<Permission> requestImplementation(final String... permissions) {
		List<Observable<Permission>> list = new ArrayList<>(permissions.length);
		List<String> unrequestedPermissions = new ArrayList<>();

		// In case of multiple permissions, we create an Observable for each of them.
		// At the end, the observables are combined to have a unique response.
		for (String permission : permissions) {
			mRxPermissionsFragment.log("Requesting permission " + permission);
			if (isGranted(permission)) {
				// Already granted, or not Android M
				// Return a granted Permission object.
				list.add(Observable.just(new Permission(permission, true, false)));
				continue;
			}

			if (isRevoked(permission)) {
				// Revoked by a policy, return a denied Permission object.
				list.add(Observable.just(new Permission(permission, false, false)));
				continue;
			}

			PublishSubject<Permission> subject = mRxPermissionsFragment.getSubjectByPermission(permission);
			// Create a new subject if not exists
			if (subject == null) {
				unrequestedPermissions.add(permission);
				subject = PublishSubject.create();
				mRxPermissionsFragment.setSubjectForPermission(permission, subject);
			}

			list.add(subject);
		}

		if (!unrequestedPermissions.isEmpty()) {
			String[] unrequestedPermissionsArray = unrequestedPermissions.toArray(new String[unrequestedPermissions.size()]);
			requestPermissionsFromFragment(unrequestedPermissionsArray);
		}
		return Observable.concat(Observable.from(list));
	}

	/**
	 * Invokes Activity.shouldShowRequestPermissionRationale and wraps
	 * the returned value in an observable.
	 * <p>
	 * In case of multiple permissions, only emits true if
	 * Activity.shouldShowRequestPermissionRationale returned true for
	 * all revoked permissions.
	 * <p>
	 * You shouldn't call this method if all permissions have been granted.
	 * <p>
	 * For SDK &lt; 23, the observable will always emit false.
	 */
	@SuppressWarnings("WeakerAccess")
	public Observable<Boolean> shouldShowRequestPermissionRationale(final Activity activity, final String... permissions) {
		if (!isMarshmallow()) {
			return Observable.just(false);
		}
		return Observable.just(shouldShowRequestPermissionRationaleImplementation(activity, permissions));
	}

	@TargetApi(Build.VERSION_CODES.M)
	private boolean shouldShowRequestPermissionRationaleImplementation(final Activity activity, final String... permissions) {
		for (String p : permissions) {
			if (!isGranted(p) && !activity.shouldShowRequestPermissionRationale(p)) {
				return false;
			}
		}
		return true;
	}

	@TargetApi(Build.VERSION_CODES.M)
	void requestPermissionsFromFragment(String[] permissions) {
		mRxPermissionsFragment.log("requestPermissionsFromFragment " + TextUtils.join(", ", permissions));
		mRxPermissionsFragment.requestPermissions(permissions);
	}

	/**
	 * Returns true if the permission is already granted.
	 * <p>
	 * Always true if SDK &lt; 23.
	 */
	@SuppressWarnings("WeakerAccess")
	public boolean isGranted(String permission) {
		return !isMarshmallow() || mRxPermissionsFragment.isGranted(permission);
	}

	/**
	 * Returns true if the permission has been revoked by a policy.
	 * <p>
	 * Always false if SDK &lt; 23.
	 */
	@SuppressWarnings("WeakerAccess")
	public boolean isRevoked(String permission) {
		return isMarshmallow() && mRxPermissionsFragment.isRevoked(permission);
	}

	boolean isMarshmallow() {
		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
	}

	void onRequestPermissionsResult(String permissions[], int[] grantResults) {
		mRxPermissionsFragment.onRequestPermissionsResult(permissions, grantResults, new boolean[permissions.length]);
	}
}
